package ztimer

import (
	"errors"
	"fmt"
	"sync"
	"time"
	"zinx/zlog"
)

type TimeWheel struct {
	name     string // timewheel名称
	interval int64  // 刻度时间间隔（ms)
	scales   int    // 每个时间轮上刻度数
	curIndex int    // 当前时间指针的指向
	maxCap   int    // 每个刻度存储timer定时器最大容量
	/**
	 * 当前时间轮上的所有timer
	 * map[int] map[uint32] *Timer, uint32表示Timer的id号
	 * map[int] VALUE  其中int表示当前时间轮的刻度
	 */
	timerQueue    map[int]map[uint32]*Timer // 每个刻度上有多少个定时器
	nextTimeWheel *TimeWheel                // 下一层时间轮
	sync.RWMutex                            // 互斥锁（继承RWMutex的 RWLock,UnLock 等方法）,继承
}

func NewTimeWheel(name string, interval int64, scales int, maxCap int) *TimeWheel {
	tw := &TimeWheel{
		name:       name,
		interval:   interval,
		scales:     scales,
		maxCap:     maxCap,
		timerQueue: make(map[int]map[uint32]*Timer, scales),
	}

	for i := 0; i < scales; i++ {
		tw.timerQueue[i] = make(map[uint32]*Timer, maxCap)
	}

	zlog.Debug("Init TimerWheel Name: ", tw.name, " Is Done!")
	return tw
}

/**
 * @fn:将一个timer定时器加入到分层时间轮中
 * @param:tid 每个定时器timer的唯一标识
 * @param:t 当前被加入时间轮的定时器
 * @param:forceNext 是否强制的将定时器添加到下一层时间轮
 */
func (tw *TimeWheel) addTimer(tid uint32, t *Timer, forceNext bool) error {
	defer func() error {
		if err := recover(); err != nil {
			errStr := fmt.Sprintf("addTimer function error %s", err)
			zlog.Error(errStr)
			return errors.New(errStr)
		}
		return nil
	}()

	// 得到当前的超时时间间隔(ms)毫秒为单位
	delayInterval := t.unixts - UnixMill()
	// 如果当前的超时时间 大于一个刻度的时间间隔
	if delayInterval >= tw.interval {
		dn := delayInterval / tw.interval
		tw.timerQueue[(tw.curIndex+int(dn))%tw.scales][tid] = t
		return nil
	}

	// 如果当前的超时时间,小于一个刻度的时间间隔，并且当前时间轮没有下一层，经度最小的时间轮
	if delayInterval < tw.interval && tw.nextTimeWheel == nil {
		if forceNext == true {
			tw.timerQueue[(tw.curIndex+1)%tw.scales][tid] = t
		} else {
			tw.timerQueue[tw.curIndex][tid] = t
		}
		return nil
	}

	// 如果当前的超时时间，小于一个刻度的时间间隔，并且有下一层时间轮
	if delayInterval < tw.interval {
		return tw.nextTimeWheel.AddTimer(tid, t)
	}
	return nil
}

/**
 * @fn:添加一个timer到一个时间轮中(非时间轮自转情况)
 * @param:tid 每个定时器timer的唯一标识
 * @param:t 当前被加入时间轮的定时器
 */
func (tw *TimeWheel) AddTimer(tid uint32, t *Timer) error {
	tw.Lock()
	defer tw.Unlock()
	return tw.addTimer(tid, t, false)
}

/**
 * @fn:删除一个定时器，根据定时器的id
 * @param:tid 每个定时器timer的唯一标识
 */
func (tw *TimeWheel) RemoveTimer(tid uint32) {
	tw.Lock()
	defer tw.Unlock()

	for i := 0; i < tw.scales; i++ {
		// 判断map的key是否存在
		if _, ok := tw.timerQueue[i][tid]; ok {
			delete(tw.timerQueue[i], tid)
		}
	}
}

/**
 * @fn:给一个时间轮添加下层时间轮 比如给小时时间轮添加分钟时间轮，给分钟时间轮添加秒时间轮
 * @param:下一层时间轮
 */
func (tw *TimeWheel) AddTimeWheel(next *TimeWheel) {
	tw.nextTimeWheel = next
	zlog.Info("Add TimerWhell [", tw.name, "]'s next [", next.name, "] is succ!")
}

/**
 * @fn 启动时间轮
 */
func (tw *TimeWheel) run() {
	for {
		// 时间轮每间隔interval一刻度时间，触发转动一次
		time.Sleep(time.Duration(tw.interval) * time.Microsecond)
		tw.Lock()
		// 取出挂载在当前刻度的全部定时器
		curTimers := tw.timerQueue[tw.curIndex]
		tw.timerQueue[tw.curIndex] = make(map[uint32]*Timer, tw.maxCap)
		for tid, timer := range curTimers {
			tw.addTimer(tid, timer, true)
		}

		// 取出下一个刻度挂载的全部定时器，进行重新添加（为什么？）
		nextTimers := tw.timerQueue[(tw.curIndex+1)%tw.scales]
		tw.timerQueue[(tw.curIndex+1)%tw.scales] = make(map[uint32]*Timer, tw.maxCap)
		for tid, timer := range nextTimers {
			tw.addTimer(tid, timer, true)
		}
		tw.curIndex = (tw.curIndex + 1) % tw.scales
		tw.Unlock()
	}
}

/**
 * @fn 非阻塞的方式让时间轮转起来
 */
func (tw *TimeWheel) Run() {
	go tw.run()
	zlog.Info("timerwheel name = ", tw.name, " is running...")
}

/**
 * @fn 获取定时器在一段时间间隔内的Timer
 * @param duation
 */
func (tw *TimeWheel) GetTimerWithIn(duration time.Duration) map[uint32]*Timer {
	// 找到最底层时间轮
	leaftw := tw
	for leaftw.nextTimeWheel != nil {
		leaftw = leaftw.nextTimeWheel
	}

	leaftw.Lock()
	defer leaftw.Unlock()
	// 返回timer集合
	timerList := make(map[uint32]*Timer)
	now := UnixMill()

	// 取出当前时间轮刻度内全部Timer
	for tid, timer := range leaftw.timerQueue[leaftw.curIndex] {
		if timer.unixts-now < int64(duration/1e6) {
			// 当前定时器已超时
			timerList[tid] = timer
			// 从时间轮上删除超时定时器
			delete(leaftw.timerQueue[leaftw.curIndex], tid)
		}
	}
	return timerList
}
